package security

import (
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"

	"gitee.com/chenhonghua/ginorigin/http/jwt"
	"gitee.com/chenhonghua/ginorigin/http/restful"
	"gitee.com/chenhonghua/ginorigin/http/router"
	"gitee.com/chenhonghua/ginorigin/http/security/role"
	"gitee.com/chenhonghua/ginorigin/log"
)

type httpmethod string

const (
	MethodGet     httpmethod = "GET"
	MethodHead    httpmethod = "HEAD"
	MethodPost    httpmethod = "POST"
	MethodPut     httpmethod = "PUT"
	MethodPatch   httpmethod = "PATCH" // RFC 5789
	MethodDelete  httpmethod = "DELETE"
	MethodOptions httpmethod = "OPTIONS"
)

func GET(policy string, uri string, handler gin.HandlerFunc) {
	regist(MethodGet, policy, uri, handler)
}

func HEAD(policy string, uri string, handler gin.HandlerFunc) {
	regist(MethodHead, policy, uri, handler)
}

func POST(policy string, uri string, handler gin.HandlerFunc) {
	regist(MethodPost, policy, uri, handler)
}

func PUT(policy string, uri string, handler gin.HandlerFunc) {
	regist(MethodPut, policy, uri, handler)
}

func PATCH(policy string, uri string, handler gin.HandlerFunc) {
	regist(MethodPatch, policy, uri, handler)
}

func DELETE(policy string, uri string, handler gin.HandlerFunc) {
	regist(MethodDelete, policy, uri, handler)
}

func OPTIONS(policy string, uri string, handler gin.HandlerFunc) {
	regist(MethodOptions, policy, uri, handler)
}

var SECURITY_HANDLER gin.HandlerFunc = func(ctx *gin.Context) {
	if id := jwt.GetClaims(ctx).Id; len(id) == 0 {
		log.Error(ErrNoCustomerInfo)
		restful.Errors.FromError(ErrNoCustomerInfo).Restful(ctx)
		ctx.Abort() // 终止后续处理
	} else if uid, e := strconv.ParseUint(id, 10, 64); e != nil || uid == 0 {
		log.Error(ErrNoCustomerInfo)
		restful.Errors.FromError(ErrNoCustomerInfo).Restful(ctx)
		ctx.Abort() // 终止后续处理
	} else if p, b := role.GetPolicy(apiObj(ctx.Request.Method, ctx.Request.URL.Path), role.ACT_API_ACCESS); !b {
		log.Error(ErrPolicyNotFound)
		restful.Errors.FromError(ErrPolicyNotFound).Restful(ctx)
		ctx.Abort() // 终止后续处理
	} else if urs, e := role.FindUserRoleByUid(uid); e != nil {
		log.Error(e)
		restful.Errors.FromError(e).Restful(ctx)
		ctx.Abort() // 终止后续处理
	} else if len(urs) == 0 {
		log.Error(ErrNoApiPolicy)
		restful.Errors.FromError(ErrNoApiPolicy).Restful(ctx)
		ctx.Abort() // 终止后续处理
	} else {
		for _, ur := range urs {
			if nil != ur.Role && ur.Role.Match(p) {
				ctx.Next()
				return
			}
		}
		// 未找到匹配的权限
		restful.Errors.FromError(ErrNoApiPolicy).Restful(ctx)
		ctx.Abort() // 终止后续处理
	}
}

// regist 函数用于注册一个新的路由处理函数，并应用安全策略。
//
// 参数包括请求方法、策略字符串、URI路径和处理函数。
// 该函数首先检查安全模块是否启用，然后根据URI获取路由组。
// 如果路由组不存在，则记录错误并退出。
// 根据请求方法，将处理函数注册到相应的路由上。
// 最后，创建一个新的策略对象，并将其添加到策略字典中。
func regist(method httpmethod, policy string, uri string, handler gin.HandlerFunc) {
	if !securityConfig.Enable {
		log.Fatal(ErrNotActive)
	} else if len(uri) == 0 || len(method) == 0 || nil == handler {
		log.Fatal(ErrNotActiveInvalidParam)
	}
	u := uri
	g, u := getGroup(uri)
	// if g == nil {
	// 	log.Fatal(ErrRegistFailed)
	// }
	iroutes := g.Use(jwt.JWT_HANDLER, SECURITY_HANDLER) // 在目标handler前，需先进行jwt和security处理。
	switch method {
	case MethodGet:
		iroutes.GET(u, handler)
	case MethodPost:
		iroutes.POST(u, handler)
	case MethodHead:
		iroutes.HEAD(u, handler)
	case MethodPut:
		iroutes.PUT(u, handler)
	case MethodDelete:
		iroutes.DELETE(u, handler)
	case MethodPatch:
		iroutes.PATCH(u, handler)
	case MethodOptions:
		iroutes.OPTIONS(u, handler)
	}
	role.SetPolicy(role.Policy{
		Sub:     policy,
		ObjType: role.OBJ_TYPE_API,
		Obj:     apiObj(string(method), uri),
		Act:     int8(role.ACT_API_ACCESS),
	})
	// log.Debugf("security注册路由:%#v\n", p)
}

// getGroup 根据给定的 URI 字符串获取对应的 gin.RouterGroup 和剩余的 URI 片段。
//
// 如果 URI 只有一个片段，则返回 nil 和该片段。
// 否则，它会递归地创建嵌套的 RouterGroup 直到倒数第二个片段，并返回最后一个片段。
//
// 参数:
//
//	uri: 需要解析的 URI 字符串
//
// 返回值:
//
//	*gin.RouterGroup: 对应的 RouterGroup，如果 URI 只有一个片段则为 nil
//	string: 剩余的 URI 片段
func getGroup(uri string) (*gin.RouterGroup, string) {
	uriSlices := splitUri(uri)
	if len(uriSlices) == 1 {
		return nil, uriSlices[0]
	}
	group := router.GetRouter().Group(uriSlices[0])
	for i := 1; i < len(uriSlices)-1; i++ {
		group = group.Group(uriSlices[i])
	}
	return group, uriSlices[len(uriSlices)-1]
}

// splitUri 根据 URI 中的冒号分割字符串，并将结果存储在一个字符串切片中。
//
// 如果 URI 包含冒号，则将其前面的部分作为前缀，后面的部分作为后缀。然后将前缀按斜杠分割，并将非空部分添加到结果切片中。
// 如果存在后缀，则将其附加到结果切片的最后一个元素上。
//
// 将“/examples/demo/d1h4”切割成[]string{"examples","demo","d1h4"}
//
// 将“/examples/demo/d1h4/:param1/:param2”切割成[]string{"examples","demo","d1h4/:param1/:param2"}
//
// 参数:
//
//	uri: 输入的 URI 字符串
//
// 返回值:
//
//	[]string: 分割后的字符串切片
func splitUri(uri string) []string {
	var prefixUri, suffixUri string
	result := []string{}
	if strings.ContainsRune(uri, ':') {
		i := strings.IndexAny(uri, ":")
		prefixUri = uri[:i]
		suffixUri = uri[i:]
	} else {
		prefixUri = uri
	}
	for _, s := range strings.Split(prefixUri, "/") {
		if len(s) == 0 {
			continue
		}
		result = append(result, s)
	}
	if len(suffixUri) > 0 {
		lastIndex := len(result) - 1
		result[lastIndex] = result[lastIndex] + "/" + suffixUri
	}
	return result
}

func apiObj(method string, relativePath string) string {
	return method + "_" + relativePath
}
